# -*- coding: UTF-8 -*-
# __author__ = 'Sengo'
from settings import C, P, SCREEN_SIZE, SHADING_MODEL, T_H, T_W, TEXTURE_FILE, ANGLE
from OpenGL.GL import *
from cal_service import transformation
from illumination import computer_I, computer_gouraud_normal
import numpy as np
from texture import read_texture, find_uv_sphere
from data_service import read_data

angle = ANGLE


def do_z_buffer():
    # 初始化z_buffer
    global angle
    glClear(GL_COLOR_BUFFER_BIT)
    depth_buffer, frame_buffer = init_z_buffer()
    scan_line(depth_buffer, frame_buffer, angle)
    glBegin(GL_POINTS)
    for x in range(len(frame_buffer)):
        for y in range(len(frame_buffer)):
            color = frame_buffer[x][y]
            glColor3f(color[0], color[1], color[2])
            glVertex2i(x, y)
    glEnd()
    glFlush()

    angle += 10
    angle %= 360


def scan_line(depth_buffer, frame_buffer, angle=None):
    # local coordinate
    local_vertexs, local_line = read_data()
    # init data
    d = 3.8
    f = 1
    h = 0.5
    V_prime = [0, 0.5, 0]  # Y-direction of camera
    vertexes, lines = transformation(C, P, V_prime, d, f, h, angle)
    gouraud_normal = computer_gouraud_normal(lines)
    # 初始化边表
    surfaces = []
    for line in lines:
        surface = []

        for v in line[:-3]:
            vertex = vertexes[v][:-1]   # 把点的顺序转成具体的点
            local_vertex = local_vertexs[v]
            vertex.append(local_vertex)  # 把它3D的local coordinate坐标加进去
            if SHADING_MODEL == 3:
                vertex.append(gouraud_normal[v])
            surface.append(vertex)

        # normal, line-of-sight, L
        surface.append(line[-3:])
        surfaces.append(surface)

    # 对每个面
    for srfc in surfaces:
        # 法向量
        others = srfc[-1]
        N = others[0]
        V = others[1]
        L = others[2]
        srfc = srfc[:-1]

        # glBegin(GL_POINTS)
        smax, smin = find_scan_line_scope(srfc)
        local_y_max, local_y_min = find_local_vertex_y_scope(srfc)
        # 创建一个面的活化表 和 AET表
        edge_table = create_edge_table(smax, smin)
        AET = edge_model()
        # 取出当前点1前后相邻的共4个点，点1与点2的连线作为本次循环处理的边，点1与点2的连线作为本次循环处理的边，
        # 另外两个点点0和点3用于计算奇点
        v_num = len(srfc)
        for i in range(v_num):
            x0 = srfc[(i-1+v_num) % v_num][0]
            x1 = srfc[i][0]
            z1 = srfc[i][2]
            x2 = srfc[(i+1) % v_num][0]
            z2 = srfc[(i+1) % v_num][2]
            x3 = srfc[(i+2) % v_num][0]
            y0 = srfc[(i-1 + v_num) % v_num][1]
            y1 = srfc[i][1]
            y2 = srfc[(i + 1) % v_num][1]
            y3 = srfc[(i + 2) % v_num][1]

            # 水平线直接舍弃
            if y1 == y2:
                continue

            # 计算下端点y坐标、上端点y坐标、下端点x坐标和斜率倒数
            ymin = y1 if y1 < y2 else y2
            ymax = y1 if y1 > y2 else y2
            xmin = x1 if y1 < y2 else x2
            dx = (x1-x2) * 1.0 / (y1-y2)

            # local_x_min
            local_x1 = srfc[i][-2][0]
            local_y1 = srfc[i][-2][1]
            local_x2 = srfc[(i+1) % v_num][-2][0]
            local_y2 = srfc[(i + 1) % v_num][-2][1]
            local_x_min = local_x1 if local_y1 < local_y2 else local_x2

            # 奇点特殊处理，若点2->1->0的y坐标单调递减则y1为奇点，若点1->2->3的y坐标单调递减则y2为奇点
            if (y2 > y1 > y0) or (y1 > y2 > y3):
                ymin += 1
                xmin += dx

            # 创建一条边【ymax, xmin, dx, vertex1, vertex2】
            edge = edge_model()
            edge.ymax = ymax
            edge.xmin = xmin
            edge.dx = dx
            edge.edge_next = edge_table[ymin]
            edge.edge_vertex1 = [x1, y1, z1]
            edge.edge_vertex2 = [x2, y2, z2]
            edge_table[ymin] = edge

            # local vertex
            edge.local_vertex1 = srfc[i][-2]
            edge.local_vertex2 = srfc[(i+1) % v_num][-2]
            edge.local_x_min = local_x_min
            if SHADING_MODEL == 3:
                edge.vertex_normal1 = srfc[i][-1]
                edge.vertex_normal2 = srfc[(i+1) % v_num][-1]

        # 扫描线从下往上扫描，y坐标每次加1
        do_scan(smin, smax, edge_table, AET, depth_buffer, frame_buffer, N, V, L, local_y_min, local_y_max)
        # glEnd()
        # glFlush()


def do_scan(ymin, ymax, edge_table, AET, depth_buffer, frame_buffer, N, V, L, local_y_min, local_y_max):
    if ymin == ymax:
        return
    d_local_y = 1.0 / (ymax-ymin) * (local_y_max-local_y_min)
    # 扫描线从下往上扫描，y坐标每次加1
    texture = read_texture(TEXTURE_FILE, T_W, T_H)
    local_y = local_y_min
    local_i = 0
    for y in range(ymin, ymax + 1):
        if local_i > 0:
            local_y += d_local_y
        local_i += 1
        # 取出ET中当前扫描行的所有边并按x的递增顺序（若x相等则按dx的递增顺序）插入AET
        while edge_table[y]:
            edge_insert = edge_table[y]  # 拿出edge_table的边来插入活化边
            edge_in_AET = AET
            # 在AET中搜索合适的插入位置 按x（若x相等则按∆x）递增方向排序。
            while edge_in_AET.edge_next:
                if edge_insert.xmin > edge_in_AET.edge_next.xmin:
                    edge_in_AET = edge_in_AET.edge_next
                    continue

                if edge_insert.xmin == edge_in_AET.edge_next.xmin and edge_insert.dx > edge_in_AET.edge_next.dx:
                    edge_in_AET = edge_in_AET.edge_next
                    continue
                # 找到位置
                break

            # 将pInsert插入AET的当前位置
            edge_table[y] = edge_insert.edge_next  # 指向下一个边
            edge_insert.edge_next = edge_in_AET.edge_next
            edge_in_AET.edge_next = edge_insert

        # AET中的边两两配对并填色
        p = AET
        while p.edge_next and p.edge_next.edge_next:
            # 左边的边的两个端点
            v1 = p.edge_next.edge_vertex1
            v2 = p.edge_next.edge_vertex2

            # 右边的边的两个端点
            v3 = p.edge_next.edge_next.edge_vertex1
            v4 = p.edge_next.edge_next.edge_vertex2
            # 左边的边的中间的点，见PPT 15页
            Za = v1[2] - (v1[2]-v2[2]) * (v1[1]-y)*1.0 / (v1[1] - v2[1])
            # 右边的边的中间的点
            Zb = v3[2] - (v3[2]-v4[2]) * (v3[1]-y)*1.0 / (v3[1] - v4[1])
            # local
            local_vertex1 = p.edge_next.local_vertex1
            local_vertex2 = p.edge_next.local_vertex2
            local_vertex3 = p.edge_next.edge_next.local_vertex1
            local_vertex4 = p.edge_next.edge_next.local_vertex2
            local_z_a = local_vertex1[2] - (local_vertex1[2]-local_vertex2[2]) * (v1[1]-y)*1.0 / (v1[1] - v2[1])
            local_z_b = local_vertex3[2] - (local_vertex3[2]-local_vertex4[2]) * (v3[1]-y)*1.0 / (v3[1] - v4[1])
            if SHADING_MODEL == 3:
                N1 = np.mat(p.edge_next.vertex_normal1)
                N2 = np.mat(p.edge_next.vertex_normal2)
                N3 = np.mat(p.edge_next.edge_next.vertex_normal1)
                N4 = np.mat(p.edge_next.edge_next.vertex_normal2)

                Na = N1 * (y - v2[1]) * 1.0 / (v1[1] - v2[1]) + N2 * (v1[1] - y) * 1.0 / (v1[1] - v2[1])
                Nb = N3 * (y - v4[1]) * 1.0 / (v3[1] - v4[1]) + N4 * (v3[1] - y) * 1.0 / (v3[1] - v4[1])

            local_ii = 0
            local_x = p.edge_next.local_x_min
            local_x_length = abs(p.edge_next.edge_next.local_x_min - p.edge_next.local_x_min)
            for x in range(int(p.edge_next.xmin), int(p.edge_next.edge_next.xmin)):
                # X, Y, Z, COLOR
                xa = int(p.edge_next.xmin)
                xb = int(p.edge_next.edge_next.xmin)
                xp = x
                Zp = Zb - (Zb - Za) * (xb - xp)*1.0 / (xb - xa)

                # local x
                if local_ii > 0:
                    local_x += 1.0 / (xb-xa) * local_x_length
                # local_xa = p.edge_next.local_x_min
                # local_xb = p.edge_next.edge_next.local_x_min
                local_z = local_z_b - (local_z_b - local_z_a) * (xb - xp)*1.0 / (xb - xa)
                u, v = find_uv_sphere(local_x, local_y, local_z, T_W, T_H)
                color = texture[v-1][u-1]
                if SHADING_MODEL == 3:
                    Np = Na * (xb - xp) * 1.0 / (xb - xa) + Nb * (xp - xa) * 1.0 / (xb - xa)
                    Np = Np.tolist()[0]
                    Ip = computer_I(Np, V, L, color)

                if x >= SCREEN_SIZE or y >= SCREEN_SIZE or x <= 0 or y <= 0:
                    continue

                if Zp < depth_buffer[x][y]:
                    depth_buffer[x][y] = Zp
                    if SHADING_MODEL == 1:
                        frame_buffer[x][y] = computer_I(N, V, L)
                    if SHADING_MODEL in (2, 3):
                        frame_buffer[x][y] = Ip

            p = p.edge_next.edge_next
        # 删除AET中满足y=ymax的边
        p = AET
        while p.edge_next:
            if p.edge_next.ymax == y:
                p.edge_next = p.edge_next.edge_next
            else:
                p = p.edge_next

        # 更新AET中边的x值，进入下一循环
        p = AET
        while p.edge_next:
            p.edge_next.xmin += p.edge_next.dx
            p = p.edge_next


def find_scan_line_scope(vertexes):
    # 在所有点中找出的Ymax和Ymin， 就是扫描线的扫描范围
    ymax = 0
    ymin = SCREEN_SIZE
    for v in vertexes:
        if v[1] > ymax:
            ymax = v[1]
        if v[1] < ymin:
            ymin = v[1]
    return ymax, ymin


def find_local_vertex_y_scope(vertexes):
    ymax = -1000
    ymin = 1000
    for v in vertexes:
        local_v = v[-2]
        if local_v[1] > ymax:
            ymax = local_v[1]
        if local_v[1] < ymin:
            ymin = local_v[1]
    return ymax, ymin


def create_edge_table(ymax, ymin):
    edge_table = {}
    for i in range(ymin, ymax+1):
        edge_table[i] = None
    return edge_table


class edge_model:
    def __init__(self):
        self.ymax = None
        self.xmin = None
        self.dx = None
        self.edge_next = None
        self.edge_vertex1 = None
        self.edge_vertex2 = None
        self.gouraud_I1 = None
        self.gouraud_I2 = None
        self.vertex_normal1 = None
        self.vertex_normal2 = None
        self.local_vertex1 = None
        self.local_vertex2 = None
        self.local_x_min = None


def init_z_buffer():
    # init depth_buffer
    depth_buffer = [[1 for col in range(SCREEN_SIZE)] for row in range(SCREEN_SIZE)]

    frame_buffer = [[(0, 0, 0) for col in range(SCREEN_SIZE)] for row in range(SCREEN_SIZE)]

    return depth_buffer, frame_buffer


if __name__ == '__main__':
    e1 = edge_model()
    e2 = edge_model()
    e3 = edge_model()
    e_in = edge_model()
    e1.dx = 1
    e2.dx = 2
    e3.dx = 3
    e_in.dx = 2.5

    e1.edge_next = e2
    e2.edge_next = e3
    e3.edge_next = None

    p = e1
    while p.edge_next:
        if e_in.dx > p.edge_next.dx:
            p = p.edge_next
            continue
        break

    e_in.edge_next = p.edge_next
    p.edge_next = e_in

    print e1.dx
    print e1.edge_next.dx
    print e1.edge_next.edge_next.dx
    print "====="